home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Sprite 1984 - 1993
/
Sprite 1984 - 1993.iso
/
src
/
cmds
/
dump
/
main.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-03-28
|
46KB
|
1,974 lines
/*
* main.c --
*
* Main routine for the sprite dump program.
*
*
* Copyright 1988 Regents of the University of California
* Permission to use, copy, modify, and distribute this
* software and its documentation for any purpose and without
* fee is hereby granted, provided that the above copyright
* notice appear in all copies. The University of California
* makes no representations about the suitability of this
* software for any purpose. It is provided "as is" without
* express or implied warranty.
*/
#ifndef lint
static char rcsid[] = "$Header: /sprite/src/admin/dump/RCS/main.c,v 1.12 92/03/28 17:29:31 kupfer Exp $";
#endif
#include <sprite.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <pwd.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/dir.h>
#include <option.h>
#include <fs.h>
#include <dev/tape.h>
#include <status.h>
#include <cfuncproto.h>
#include <varargs.h>
#include <assert.h>
#include <dev/exabyte.h>
#include <bstring.h>
#include <sys/errno.h>
#define DEBUG
#ifdef DEBUG
#define debugp(x) ((void) fprintf x, fflush(stderr))
#else
#define debugp(x)
#endif
#define SPRITE_OLD_DUMP_HEADER "SPRITE DUMP TAPE #"
#define SPRITE_DUMP_HEADER "SPRITE DUMP TAPE,"
#define SPRITE_DUMP_INFO " Version %d Level %d Tape %d"
#define SPRITE_DUMP_VERSION 1
#define DUMPDATES "/sprite/admin/dump/dumpdates"
#define LOGFILE "/sprite/admin/dump/dumplog"
#define DRIVELOG "/sprite/admin/dump/statuslog"
#define TAR "tar.gnu"
#define KBytes(n) (n * 1024)
#define MBytes(n) (1024 * KBytes(n))
#define IOBUF_SIZE KBytes(62) /* Exebyte likes 62KB chunks */
#define TOC_SIZE_OLD MBytes(10) /* TOC occupied 10 MB on the tape */
#define TOC_USEABLE_OLD MBytes(2) /* About 2 MB of it can be used */
#define TOC_SIZE KBytes(16) /* No longer needs to be big. */
#define TOC_USEABLE TOC_SIZE /* and we can use it all */
#define TAR_BLOCK_SIZE KBytes(64) /* Do the tar in 64K blocks. */
#define LABEL_SIZE_OLD IOBUF_SIZE /* Size of useable label. */
#define LABEL_SIZE TOC_SIZE /* Size of useable label. */
static char tapeBuffer[IOBUF_SIZE];
static char *directoryToDump;
static tarChild;
static int pipefd[2];
static int archivefd;
static int dumpLevel = 0;
static int tapeNumber;
static int tapeLevel;
static int fileNumber;
static int verbose;
static char *archiveFileName;
static char *mailWho;
static time_t lastTime;
static time_t startTime;
static int fatalErrors;
static int nonFatalErrors;
static int archiveFileIsATapeDrive;
static int printTOC;
static int progArgc;
static char **progArgv;
static int reInitialize = 0;
static int reInitializeSafe = 0;
static long totalBytes;
static int debug;
static int oldFormat;
static int tarBlocks;
static int tapePosition = 0;
static int version = 0;
static int labelSize = 0;
static int lastFile;
static int official = 1;
#ifdef PROG_RESTORE
static int printContents = 0;
#endif
#ifdef PROG_DUMP
static int resetAccessTimes;
static Option OptionArray[] = {
{ OPT_TRUE, "a", (char *) &resetAccessTimes, "Reset access times" },
{ OPT_TRUE, "d", (char *) &debug, "Debug" },
{ OPT_STRING, "f", (char *) &archiveFileName, "Name of archive file" },
{ OPT_INT, "i", (char *) &tapeNumber, "Initialize the tape" },
{ OPT_INT, "l", (char *) &dumpLevel, "Dump level (0-9)" },
{ OPT_STRING, "m", (char *) &mailWho, "Send mail upon completion" },
{ OPT_TRUE, "r", (char *) &reInitialize, "Re-initialize the tape" },
{ OPT_TRUE, "s", (char *) &reInitializeSafe, "Safely re-initialize tape" },
{ OPT_TRUE, "t", (char *) &printTOC, "Print table of contents" },
{ OPT_TRUE, "v", (char *) &verbose, "Verbose" },
{ OPT_FALSE, "u", (char *) &official, "Unofficial dump" },
};
#endif
#ifdef PROG_RESTORE
static int relativePaths;
static Option OptionArray[] = {
{ OPT_STRING, "f", (char *) &archiveFileName, "Name of archive" },
{ OPT_TRUE, "d", (char *) &debug, "Debug" },
{ OPT_INT, "n", (char *) &fileNumber, "File number to use" },
{ OPT_TRUE, "r", (char *) &relativePaths, "Use relative pathnames" },
{ OPT_TRUE, "t", (char *) &printTOC, "Print table of contents" },
{ OPT_TRUE, "v", (char *) &verbose, "Verbose" },
{ OPT_TRUE, "T", (char *) &printContents, "Print contents of dump file"},
};
#endif
static void cleanup_sighup _ARGS_((int sig));
static void cleanup_sigint _ARGS_((int sig));
static void cleanup_sigquit _ARGS_((int sig));
static void cleanup_sigpipe _ARGS_((int sig));
static void cleanup_sigterm _ARGS_((int sig));
static void openArchiveFile _ARGS_((void));
static void forkOffTar _ARGS_((void));
static void dumpDirectory _ARGS_((char *dir));
static void initializeTape _ARGS_((void));
static void sendMail _ARGS_((const char *msg));
static void rewindTape _ARGS_((void));
static void fatal _ARGS_((char *fmt, ...));
static void warning _ARGS_((char *fmt, ...));
static void readTapeLabel _ARGS_((void));
static void rewindTape _ARGS_((void));
static void openLog _ARGS_((void));
static void skipOverFiles _ARGS_((void));
#ifdef PROG_RESTORE
static void findLastFile _ARGS_((void));
#endif
static void waitForChildToDie _ARGS_((void));
static void setFileNumber _ARGS_((void));
static void quote_string _ARGS_((char *to, const char *from));
static void checkTape _ARGS_((void));
static int parseDumpInfo _ARGS_((char *buf));
static void gotoEOD _ARGS_((void));
static ReturnStatus skipFiles _ARGS_((int num));
#ifdef PROG_DUMP
static int getNextDumpDateEntry _ARGS_((void));
static void writeTapeLabel _ARGS_((void));
static void flushOutput _ARGS_((void));
static void getDumpDate _ARGS_((void));
static void recordTime _ARGS_((void));
#endif
#ifdef __STDC__
#define VOID void
#endif
void
main(argc, argv)
int argc;
char **argv;
{
uid_t root;
uid_t dumper;
struct passwd *pwPtr;
uid_t euid;
extern uid_t geteuid();
extern int setreuid();
#ifdef PROG_DUMP
ReturnStatus status;
#endif
char *date;
int i;
signal(SIGHUP, cleanup_sighup);
signal(SIGINT, cleanup_sigint);
signal(SIGQUIT, cleanup_sigquit);
signal(SIGPIPE, cleanup_sigpipe);
signal(SIGTERM, cleanup_sigterm);
startTime = time(0L);
date = asctime(localtime(&startTime));
date[24] = '\0';
debugp((stderr, "\n"));
for (i = 0; i < argc; i++) {
debugp((stderr, "%s ", argv[i]));
}
debugp((stderr, "\n"));
debugp((stderr, "%s\n", date));
argc = Opt_Parse(argc, argv, OptionArray, Opt_Number(OptionArray), 0);
progArgc = argc;
progArgv = argv;
pwPtr = getpwnam("root");
if (pwPtr != (struct passwd *) 0) {
root = pwPtr->pw_uid;
pwPtr = getpwnam("dumper");
if (pwPtr != (struct passwd *) 0) {
dumper = pwPtr->pw_uid;
euid = geteuid();
if (euid == root && setreuid(dumper, -1) != 0) {
perror("Couldn't set real userID to dumper");
exit(1);
}
}
}
endpwent();
if ((TAR_BLOCK_SIZE & (512 - 1)) != 0) {
fatal("Tar block size must be multiple of 512\n");
}
tarBlocks = TAR_BLOCK_SIZE / 512;
oldFormat = 0;
openLog();
if (archiveFileName == NULL) {
fatal("No archive file specified");
}
tapeLevel = dumpLevel;
if (tapeNumber != 0) {
openArchiveFile();
initializeTape();
close(archivefd);
if (argc < 2) {
exit(EXIT_SUCCESS);
}
}
openArchiveFile();
if (archiveFileIsATapeDrive) {
readTapeLabel();
if (reInitialize || reInitializeSafe) {
if (reInitializeSafe) {
checkTape();
}
initializeTape();
close(archivefd);
if (argc < 2) {
exit(EXIT_SUCCESS);
}
openArchiveFile();
}
}
if (printTOC) {
if (!archiveFileIsATapeDrive) {
fatal("Cannot print TOC: Archive is not a tapeDrive");
}
fprintf(stderr, "%s\n", tapeBuffer);
if (argc < 2) {
exit(EXIT_SUCCESS);
}
}
if (argc < 2) {
Opt_PrintUsage(argv[0], OptionArray, Opt_Number(OptionArray));
exit(EXIT_FAILURE);
}
#ifdef PROG_DUMP
directoryToDump = argv[1];
debugp((stderr, "dumping %s\n", directoryToDump));
if (archiveFileIsATapeDrive) {
setFileNumber();
if (oldFormat) {
skipOverFiles();
} else {
/*
* We are currently at the end of the label since we just read
* it. The tar file will follow the subsequent file mark.
*/
status = skipFiles(1);
if (status != SUCCESS) {
fatal("Can't skip over last filemark\n");
}
}
}
getDumpDate();
forkOffTar();
dumpDirectory(directoryToDump);
flushOutput();
waitForChildToDie();
openArchiveFile();
if (fatalErrors) {
sendMail("Dump failed with fatal errors");
} else {
if (nonFatalErrors) {
sendMail("Dump completed with non-fatal errors.");
} else {
sendMail("Dump completed successfully.");
}
recordTime();
}
debugp((stderr, "finished dumping %s, %.1lf MBytes\n",
directoryToDump, (double) totalBytes / (double) MBytes(1)));
#else /* PROG_DUMP */
assert(progArgc >= 2);
if (fileNumber == 0) {
setFileNumber();
} else {
findLastFile();
}
skipOverFiles();
forkOffTar();
waitForChildToDie();
openArchiveFile();
rewindTape();
#endif /* PROG_DUMP */
debugp((stderr,
"%s exiting, there were %d non-fatal errors, %d hard errors\n",
argv[0], nonFatalErrors, fatalErrors));
exit(fatalErrors);
}
/*
*----------------------------------------------------------------------
*
* dumpDirectory --
*
* Procedure to dump a directory tree. This routine does an
* inorder traversal of a directory. It calls itself recursively
* to dump each subdirectory.
*
* Results:
* None.
*
* Side effects:
* Depends on the options, but normally the files in the specified
* directory are dumped.
*
*----------------------------------------------------------------------
*/
static void
dumpDirectory(dir)
char *dir;
{
char pathname[MAXPATHLEN];
char backslash_pathname[MAXPATHLEN];
DIR *dirDesc;
struct direct *d;
int slash;
if ((dirDesc = opendir(dir)) == NULL) {
warning("Cannot open directory `%s'", dir);
++nonFatalErrors;
return;
}
slash = (dir[strlen(dir) - 1] == '/');
while ((d = readdir(dirDesc)) != NULL) {
struct stat statBuf;
if (*d->d_name == '.') {
if (d->d_name[1] == 0)
continue;
if (d->d_name[1] == '.' && d->d_name[2] == 0)
continue;
}
(void) sprintf(pathname, slash ? "%s%s" : "%s/%s", dir, d->d_name);
if (lstat(pathname, &statBuf)) {
warning("can't lstat %s", pathname);
++nonFatalErrors;
continue;
}
quote_string(backslash_pathname, pathname);
if (statBuf.st_mtime >= lastTime || statBuf.st_ctime >= lastTime) {
if (puts(backslash_pathname) == EOF) {
(void) fprintf(stderr, "error in puts\n");
++fatalErrors;
} else if (verbose) {
fprintf(stderr, "dumping %s\n", backslash_pathname);
}
totalBytes += statBuf.st_size;
}
if (((statBuf.st_mode & S_IFDIR) == S_IFDIR) &&
((statBuf.st_mode & S_IFRLNK) != S_IFRLNK) &&
((statBuf.st_mode & S_IFPDEV) != S_IFPDEV)) {
dumpDirectory(pathname);
}
}
(void) closedir(dirDesc);
return;
}
static struct {
int tape;
int file;
int level;
double mbytes;
double remaining;
double errorRate;
time_t time;
char dirname[MAXPATHLEN];
} dumpDateEntry;
#ifdef PROG_DUMP
/*
*----------------------------------------------------------------------
*
* recordTime --
*
* Append the start time of the current dump to the dump database.
*
* Results:
* None.
*
* Side effects:
* The start time of the current dump is appended to the end
* of the dump database. A fatal error occurs if the database
* doesn't exist or is unwritable.
*
*----------------------------------------------------------------------
*/
static void
recordTime()
{
FILE *fp = NULL;
char *date;
Dev_TapeStatus tapeStatus;
int errors;
double mbytes;
double errorRate;
ReturnStatus status = FAILURE;
double remaining;
double mbytesPerBlock;
char *name = "UNKNOWN";
char *serial = "UNKNOWN";
int blocks;
char shortDate[16];
char buf[128];
if (official) {
if ((fp = fopen(DUMPDATES, "a")) == NULL) {
fatal("Can't open %s", DUMPDATES);
}
}
date = asctime(localtime(&startTime));
date[24] = '\0';
mbytes = ((double) totalBytes) / ((double) MBytes(1));
debugp((stderr, "mbytes = %lf\n", mbytes));
errorRate = -1;
remaining = -1;
if (!oldFormat) {
bzero((char *) &tapeStatus, sizeof(tapeStatus));
debugp((stderr, "Getting tape status\n"));
status = Fs_IOControl(archivefd, IOC_TAPE_STATUS, 0, NULL,
sizeof(tapeStatus), &tapeStatus);
if (status == SUCCESS) {
mbytesPerBlock =
(double) tapeStatus.blockSize / (double) MBytes(1);
debugp((stderr, "position = %d, remaining = %d\n",
tapeStatus.position, tapeStatus.remaining));
blocks = tapeStatus.position - tapePosition;
mbytes = blocks * mbytesPerBlock;
errors = tapeStatus.readWriteRetry + tapeStatus.dataError;
errorRate = ((double) errors / (double) blocks) * 100.0;
remaining = tapeStatus.remaining * mbytesPerBlock;
debugp((stderr,
"mbytes = %lf, errorRate = %lf, remaining = %lf\n",
mbytes, errorRate, remaining));
}
}
(void) sprintf(buf, "%03d %03d %d %6.1lf %6.1lf %s %s\n",
tapeNumber, fileNumber, dumpLevel, mbytes, remaining,
date, directoryToDump);
if (archiveFileIsATapeDrive) {
strcat(tapeBuffer, buf);
writeTapeLabel();
if (close(archivefd) != 0) {
fatal("Close of archive failed");
}
}
if (official) {
fprintf(fp, "%s", buf);
(void) fclose(fp);
}
if ((fp = fopen(DRIVELOG, "a")) == NULL) {
fatal("Can't open %s", DRIVELOG);
}
if (status == SUCCESS) {
switch(tapeStatus.type) {
case DEV_TAPE_EXB8200:
name = "EXB-8200";
break;
case DEV_TAPE_EXB8500:
name = "EXB-8500";
break;
case DEV_TAPE_TLZ04:
name = "DEC-TLZ04";
break;
default: {
if (tapeStatus.type & DEV_TAPE_8MM) {
name = "UNKNOWN-8MM";
} else if (tapeStatus.type & DEV_TAPE_4MM) {
name = "UNKNOWN-4MM";
}
}
}
if (tapeStatus.serial[0] != '\0') {
serial = tapeStatus.serial;
}
}
bcopy(&date[4], shortDate, 7);
bcopy(&date[20], &shortDate[7], 4);
shortDate[11] = '\0';
fprintf(fp, "%-10s %-10s %3d %7.1lf %5.1lf%% %s %s\n", name, serial,
tapeNumber, mbytes, errorRate, shortDate, archiveFileName);
(void) fclose(fp);
return;
}
/*
*----------------------------------------------------------------------
*
* getNextDumpDateEntry --
*
* Reads the next entry from the dump database file,
* and puts the information into a structure.
*
* Results:
* Returns non-zero if the next entry is read successfully.
*
* Side effects:
* A fatal error occurs if the database doesn't exist, is unreadable
* or if it is improperly formated.
*
*----------------------------------------------------------------------
*/
static int
getNextDumpDateEntry()
{
static FILE *fp;
static int lineCnt;
char buf[0x1000];
char *s;
if (fp == NULL) {
if ((fp = fopen(DUMPDATES, "r")) == NULL) {
fatal("Can't open %s", DUMPDATES);
}
}
/*
* read in the file line by line until we get a valid entry.
*/
while (fgets(buf, sizeof(buf), fp)) {
++lineCnt;
/*
* If the line is empty, only white space, or is a
* comment, then throw it away and continue.
*/
s = buf;
while (*s == ' ' || *s == '\t') {
++s;
}
if (*s == '#' || *s == '\n' || *s == '\0') {
continue;
}
if (parseDumpInfo(s) == 0) {
fatal("format error in %s: line %d |%s|",
DUMPDATES, lineCnt, buf);
}
return 1;
}
(void) fclose(fp);
return 0;
}
#endif
static int
parseDumpInfo(buf)
char *buf;
{
extern time_t mktime();
static char months[][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
struct tm tm;
char *s;
int i;
int n;
int token = 1;
bzero((char *) &dumpDateEntry, sizeof(dumpDateEntry));
/*
* The first token is the tape number
*/
if ((s = strtok(buf, " \t\n")) == NULL || !isdigit(*s)) {
goto mismatch;
}
dumpDateEntry.tape = atoi(s);
token++;
/*
* The next token is the file number
*/
if ((s = strtok(NULL, " \t\n")) == NULL || !isdigit(*s)) {
goto mismatch;
}
dumpDateEntry.file = atoi(s);
token++;
/*
* The next token should be a single digit in the range 0-9 that
* indicates the level of the dump.
*/
s = strtok((char *) NULL, " \t\n");
if (s == NULL || !isdigit(*s) || strlen(s) != 1) {
goto mismatch;
}
dumpDateEntry.level = *s - '0';
token++;
/* the next token should be the number of bytes */
s = strtok((char *) NULL, " \t\n");
if (strchr(s, (int) '.')) {
/* Number of MBytes. */
n = sscanf(s, "%lf", &dumpDateEntry.mbytes);
if (n != 1) {
goto mismatch;
}
token++;
/* Number of remaining MBytes on tape. */
s = strtok((char *) NULL, " \t\n");
n = sscanf(s, "%lf", &dumpDateEntry.remaining);
if (n != 1) {
goto mismatch;
}
token++;
} else {
n = sscanf(s, "%d", &i);
if (n != 1) {
goto mismatch;
}
}
/*
* The time should be formated just like the return string
* from asctime(3). So, parse the string and make sure the
* format is correct.
*/
if ((s = strtok((char *) NULL, " \t")) == NULL) {
goto mismatch;
}
token++;
if ((s = strtok((char *) NULL, " ")) == NULL) {
goto mismatch;
}
token++;
for (i = 13; --i >= 0;) {
if (strcmp(months[i], s) == 0) {
break;
}
}
if ((tm.tm_mon = i) < 0) {
goto mismatch;
}
if ((s = strtok((char *) NULL, " ")) == NULL) {
goto mismatch;
}
token++;
tm.tm_mday = atoi(s);
if ((s = strtok((char *) NULL, ":")) == NULL) {
goto mismatch;
}
token++;
tm.tm_hour = atoi(s);
if ((s = strtok((char *) NULL, ":")) == NULL) {
goto mismatch;
}
token++;
tm.tm_min = atoi(s);
if ((s = strtok((char *) NULL, " ")) == NULL) {
goto mismatch;
}
token++;
tm.tm_sec = atoi(s);
if ((s = strtok((char *) NULL, " \t\n")) == NULL) {
goto mismatch;
}
tm.tm_year = atoi(s) - 1900;
/* Convert to old-fashioned calander time */
if((dumpDateEntry.time = mktime(&tm)) == -1) {
debugp((stderr, "parseDumpInfo: mktime failed\n"));
return 0;
}
token++;
if ((s = strtok((char *) NULL, " \t\n")) == NULL) {
goto mismatch;
}
/*
* The remainder of the line is the directory name.
*/
strcpy(dumpDateEntry.dirname, s);
return 1;
mismatch:
debugp((stderr, "parseDumpInfo: parse error on token %d \"%s\"\n",
token, s));
return 0;
}
#ifdef PROG_DUMP
/*
*----------------------------------------------------------------------
*
* getDumpDate --
*
* Reads the dump database file, and determines the last time
* that a lower level dump of the directory was done.
*
* Results:
* Returns the time of the last lower level dump.
*
* Side effects:
* A fatal error occurs if the database doesn't exist, is unreadable
* or if it is improperly formated.
*
*----------------------------------------------------------------------
*/
static void
getDumpDate()
{
time_t t = 0;
while (getNextDumpDateEntry()) {
if (strcmp(dumpDateEntry.dirname, directoryToDump) != 0) {
continue;
}
if (dumpDateEntry.level < dumpLevel) {
t = dumpDateEntry.time;
}
}
lastTime = t;
return;
}
#endif
/*
*----------------------------------------------------------------------
*
* openArchiveFile --
*
* Opens the archive file and checks to see if it is a tapedrive.
*
* Results:
* None
*
* Side effects:
* Opens the archive file.
*
*----------------------------------------------------------------------
*/
static void
openArchiveFile()
{
struct stat statBuf;
extern int Fs_GetAttributes();
Fs_Attributes attrs;
Dev_TapeStatus tapeStatus;
ReturnStatus status;
debugp((stderr, "opening %s as archive file\n", archiveFileName));
#ifdef PROG_DUMP
if (strcmp(archiveFileName, "-") == 0) {
archivefd = 1;
} else if ((archivefd = open(archiveFileName,O_RDWR)) < 0) {
if ((archivefd = open(archiveFileName,
O_RDWR|O_CREAT, 0664)) < 0) {
fatal("Can't open `%s'", archiveFileName);
}
}
#else
if (strcmp(archiveFileName, "-") == 0) {
archivefd = 0;
} else if ((archivefd = open(archiveFileName,O_RDONLY)) < 0) {
fatal("Can't open `%s'", archiveFileName);
}
#endif
if (fstat(archivefd, &statBuf)) {
fatal("Cannot stat %s", archiveFileName);
}
switch (statBuf.st_mode & S_IFMT) {
case S_IFREG: /* regular file */
break;
case S_IFCHR: /* character dev. -- probably a tape drive */
if (Fs_GetAttributes(archiveFileName, 0, &attrs) != SUCCESS) {
fatal("Cannot get attributes of %s", archiveFileName);
}
if (attrs.devType == 5) {
archiveFileIsATapeDrive = TRUE;
bzero((char *) &tapeStatus, sizeof(tapeStatus));
status = Fs_IOControl(archivefd, IOC_TAPE_STATUS, 0, NULL,
sizeof(tapeStatus), &tapeStatus);
debugp((stderr, "IOC_TAPE_STATUS returned 0x%x\n", status));
if (status != SUCCESS) {
oldFormat = TRUE;
debugp((stderr, "Using old format.\n", status));
} else {
debugp((stderr, "tapeStatus.type is 0x%x\n", tapeStatus.type));
if (tapeStatus.type == DEV_TAPE_EXB8200) {
oldFormat = TRUE;
debugp((stderr, "Using old format.\n", status));
}
#ifndef PROG_DUMP
if ((tapeStatus.type == DEV_TAPE_EXB8500) &&
(tapeStatus.density == DEV_EXB8500_8200_MODE)) {
oldFormat = TRUE;
debugp((stderr, "Using old format.\n", status));
}
#endif
}
break;
}
/* FALLTHROUGH */
default:
warning("%s: not a normal file or tape drive", archiveFileName);
break;
}
return;
}
/*
*----------------------------------------------------------------------
*
* forkOffTar --
*
* Forks off tar and pipes stdout into it.
*
* Results:
* None
*
* Side effects:
* Creates a child process.
*
*----------------------------------------------------------------------
*/
static void
forkOffTar()
{
char tarargs[20];
char blocks[20];
Dev_TapeStatus tapeStatus;
ReturnStatus status;
#ifdef PROG_RESTORE
char **arg;
int i, j;
#endif
if (!oldFormat) {
bzero((char *) &tapeStatus, sizeof(tapeStatus));
status = Fs_IOControl(archivefd, IOC_TAPE_STATUS, 0, NULL,
sizeof(tapeStatus), &tapeStatus);
if (status != SUCCESS) {
debugp((stderr, "IOC_TAPE_STATUS returned 0x%x\n", status));
} else {
tapePosition = tapeStatus.position;
debugp((stderr, "position = %d\n", tapePosition));
}
}
#ifdef PROG_DUMP
if (pipe(pipefd) < 0) {
fatal("Cannot open pipe");
}
switch (tarChild = fork()) {
case -1:
fatal("Can't fork");
case 0:
(void) close(pipefd[1]);
if (dup2(archivefd, 1) < 0) {
fatal("dup2");
}
if (dup2(pipefd[0], 0) < 0) {
fatal("dup2");
}
*tarargs = '\0';
if (resetAccessTimes) {
strcat(tarargs, "a");
}
if (verbose) {
strcat(tarargs, "v");
}
if (debug) {
strcat(tarargs, "e");
}
if (oldFormat) {
strcat(tarargs, "ncfTPL");
debugp((stderr, "execing tar %s - -\n", tarargs));
execlp(TAR, TAR, tarargs, "-", "-", NULL);
} else {
strcat(tarargs, "ncbfTPL");
sprintf(blocks, "%d", tarBlocks);
debugp((stderr, "execing tar %s %s - -\n", tarargs, blocks));
execlp(TAR, TAR, tarargs, blocks, "-", "-", NULL);
}
fatal("exec failed");
default:
(void) close(pipefd[0]);
if (dup2(pipefd[1], 1)) {
fatal("dup2");
}
}
#else
sprintf(blocks, "%d", tarBlocks);
close(archivefd);
switch (tarChild = fork()) {
case -1:
fatal("Can't fork");
case 0: {
char **ptr;
arg = (char **) malloc(sizeof(char **) * (progArgc + 5));
ptr = arg;
*ptr++ = TAR;
if (printContents) {
strcpy(tarargs, "tv");
} else {
strcpy(tarargs, "x");
}
if (oldFormat) {
strcat(tarargs, "pf");
} else {
strcat(tarargs, "pfb");
}
if (verbose) {
strcat(tarargs, "v");
}
if (!relativePaths) {
strcat(tarargs, "P");
}
if (debug) {
strcat(tarargs, "e");
}
*ptr++ = tarargs;
*ptr++ = archiveFileName;
if (!oldFormat) {
*ptr++ = blocks;
}
for (i = 1; (*ptr++ = progArgv[i]) != NULL; ++i) {
continue;
}
debugp((stderr, "execing tar"));
for (j = 0; j < i + 3; j++) {
debugp((stderr, " %s", arg[j]));
}
debugp((stderr, "\n"));
execvp(TAR, arg);
fatal("exec failed");
}
default:
break;
}
#endif
debugp((stderr, "successfully forked tar\n"));
return;
}
#ifdef PROG_DUMP
/*
*----------------------------------------------------------------------
*
* flushOutput --
*
* Wait for tar to finish reading all the data in the pipe.
* Check the error return code.
*
* Results:
* None
*
* Side effects:
* Closes files, increments a global variable.
*
*----------------------------------------------------------------------
*/
static void
flushOutput()
{
debugp((stderr, "flushing output\n"));
(void) fflush(stdout);
(void) close(1);
(void) close(pipefd[1]);
return;
}
#endif
static void
waitForChildToDie()
{
int w;
union wait ws;
while ((w = wait(&ws)) > 0 && w != tarChild) {
continue;
}
switch (ws.w_retcode) {
case 0: /* normal exit */
break;
case 1:
fatal("tar invoked with invalid arguments");
break;
case 2:
warning("tar encountered at least one invalid filename");
++nonFatalErrors;
break;
case 3:
fatal("bad archive");
break;
case 4:
fatal("system error in tar");
break;
default:
fatal("tar exited with nozero status: %d", ws.w_retcode);
break;
}
(void) close(archivefd);
return;
}
/*
*----------------------------------------------------------------------
*
* readTapeLabel --
*
* Read the label on a tape, and put the label into the tapeBuffer.
*
* Results:
* None
*
* Side effects:
* The tape is rewound.
*
*----------------------------------------------------------------------
*/
static void
readTapeLabel()
{
ReturnStatus status = SUCCESS;
int n;
int bytesRead;
Dev_TapeStatus tapeStatus;
debugp((stderr, "reading tape label\n"));
labelSize = LABEL_SIZE_OLD;
bzero((char *) &tapeStatus, sizeof(tapeStatus));
status = Fs_IOControl(archivefd, IOC_TAPE_STATUS, 0, NULL,
sizeof(tapeStatus), &tapeStatus);
if ((status != SUCCESS) || (tapeStatus.type == DEV_TAPE_EXB8200) ||
(reInitialize) || (reInitializeSafe)) {
/*
* The tape may be in the old format if the tape drive is an
* exb8200, or if we are reinitializing an old format tape.
* Try reading the label at the beginning of the tape.
*/
rewindTape();
bytesRead = read(archivefd, tapeBuffer, labelSize);
debugp((stderr, "attempt to read old label read %d bytes\n",
bytesRead));
if (bytesRead == labelSize) {
if (!strncmp(tapeBuffer, SPRITE_OLD_DUMP_HEADER,
strlen(SPRITE_OLD_DUMP_HEADER)) != 0) {
oldFormat = TRUE;
debugp((stderr, "Using old format\n"));
n = sscanf(tapeBuffer + strlen(SPRITE_OLD_DUMP_HEADER),
"%d", &tapeNumber);
if (n != 1) {
warning("Couldn't read tape number from label\n");
}
} else if (oldFormat) {
fatal("The tape does not have a correct label");
}
}
}
if (!oldFormat) {
Boolean beginning = FALSE;
labelSize = LABEL_SIZE;
gotoEOD();
/*
* Keep backing up one file at a time until we find a label, or
* the beginning of the tape. In order to back up one file at
* a time we must back up over both the current file and the
* previous file, hence the skip backwards over two files.
*
*/
status = SUCCESS;
while(status == SUCCESS) {
status = skipFiles(-2);
if (status == DEV_END_OF_TAPE) {
/*
* We must have hit the beginning of the tape.
* Keep track so we only do this once.
*/
debugp((stderr, "hit beginning of tape\n"));
if (beginning == TRUE) {
fatal("The tape does not have a correct label");
}
beginning = TRUE;
status = SUCCESS;
} else if (status == SUCCESS) {
/*
* We are now on the BOT side of a file mark. Skip to the
* EOT side.
*/
debugp((stderr, "skipping over file mark\n"));
status = skipFiles(1);
if (status != SUCCESS) {
fatal("Error skipping over file mark");
}
}
/*
* Try reading the label.
*/
debugp((stderr, "trying to read label (%d bytes)\n",
labelSize));
bytesRead = read(archivefd, tapeBuffer, labelSize);
if (bytesRead == labelSize) {
debugp((stderr, "read succeeded\n"));
if (!strncmp(tapeBuffer, SPRITE_DUMP_HEADER,
strlen(SPRITE_DUMP_HEADER))){
/*
* We found a label.
*/
debugp((stderr, "found a label\n"));
break;
} else {
debugp((stderr, "found \"%s\"\n", tapeBuffer));
}
} else {
debugp((stderr, "only read %d bytes\n", bytesRead));
}
}
if (status != SUCCESS) {
fatal("The tape does not have a correct label");
}
n = sscanf(tapeBuffer + strlen(SPRITE_DUMP_HEADER),
"%*s %d %*s %d %*s %d\n", &version, &tapeLevel,
&tapeNumber);
if (n != 3) {
fatal("Couldn't read information from tape label\n");
}
if (version != SPRITE_DUMP_VERSION) {
fatal("Invalid dump tape version \"%d\"\n", version);
}
}
debugp((stderr, "Tape: %d\n", tapeNumber));
debugp((stderr, "Version: %d\n", version));
debugp((stderr, "Level: %d\n", tapeLevel));
debugp((stderr, "TapeLabel=|%s|\n", tapeBuffer));
return;
}
#ifdef PROG_DUMP
/*
*----------------------------------------------------------------------
*
* writeTapeLabel --
*
* Copy the tapeBuffer onto the tape.
*
* Results:
* None
*
* Side effects:
* The tape is rewound.
*
*----------------------------------------------------------------------
*/
static void
writeTapeLabel()
{
debugp((stderr, "writing tape label\n"));
if (oldFormat) {
rewindTape();
} else {
gotoEOD();
}
if (write(archivefd, tapeBuffer, labelSize) != labelSize) {
fatal("Error writing tape label");
}
/*
* Rewinding the tape prevents a file mark from being written, which
* is what we want on the old format tapes.
*/
if (oldFormat) {
rewindTape();
}
return;
}
#endif
static void
initializeTape()
{
int nbufs;
FILE *fp;
Dev_TapeStatus tapeStatus;
int tocSize;
static char tempBuffer[IOBUF_SIZE];
ReturnStatus status;
debugp((stderr, "initializing tape #%d\n", tapeNumber));
if (archiveFileIsATapeDrive == 0) {
fatal("Cannot initialize: Archive file is not a tape drive");
}
rewindTape();
bzero((char *) &tapeStatus, sizeof(tapeStatus));
status = Fs_IOControl(archivefd, IOC_TAPE_STATUS, 0, NULL,
sizeof(tapeStatus), &tapeStatus);
if ((status != SUCCESS) || (tapeStatus.type == DEV_TAPE_EXB8200)) {
oldFormat = TRUE;
labelSize = LABEL_SIZE_OLD;
} else {
oldFormat = FALSE;
labelSize = LABEL_SIZE;
}
memset(tapeBuffer, '\0', labelSize);
if (oldFormat) {
sprintf(tapeBuffer, "%s%d\n", SPRITE_OLD_DUMP_HEADER, tapeNumber);
} else {
char format[80];
strcpy(format, SPRITE_DUMP_HEADER);
strcat(format, SPRITE_DUMP_INFO);
sprintf(tapeBuffer, format, SPRITE_DUMP_VERSION, tapeLevel,tapeNumber);
strcat(tapeBuffer, "\n");
}
debugp((stderr, "writing label\n"));
if (write(archivefd, tapeBuffer, labelSize) != labelSize) {
fatal("Write error while initializing tape label");
}
debugp((stderr, "done writing label\n"));
if (oldFormat) {
tocSize = TOC_SIZE_OLD;
} else {
tocSize = TOC_SIZE;
}
debugp((stderr, "writing padding\n"));
memset(tempBuffer, '\0', sizeof(tempBuffer));
for (nbufs = tocSize / IOBUF_SIZE; --nbufs > 0;) {
if (write(archivefd, tempBuffer,
sizeof(tempBuffer)) != sizeof(tempBuffer)) {
fatal("Write error while initializing tape label");
}
}
debugp((stderr, "done writing padding\n"));
if (!oldFormat) {
Dev_TapeCommand args;
args.command = IOC_TAPE_WEOF;
args.count = 1;
status = Fs_IOControl(archivefd, IOC_TAPE_COMMAND, sizeof(args),
(Address)&args, 0, (Address) 0);
if (status != SUCCESS) {
fatal("Can't write file mark(s), status = 0x%x", status);
exit(status);
}
}
rewindTape();
if (official) {
if ((fp = fopen(DUMPDATES, "a")) == NULL) {
fatal("Can't open %s", DUMPDATES);
}
/*
* Put a comment in the dump data file.
*/
fprintf(fp, "# Initializing tape number %03d\n", tapeNumber);
(void) fclose(fp);
}
return;
}
static void
rewindTape()
{
int oldOffset;
int status;
debugp((stderr, "rewinding tape ...\n"));
status = Ioc_Reposition(archivefd, IOC_BASE_ZERO, 0, &oldOffset);
if (status != SUCCESS) {
fatal("Can't rewind tape drive, status = 0x%08x", status);
}
debugp((stderr, "done rewinding tape.\n"));
return;
}
static void
setFileNumber()
{
char *s;
#ifdef PROG_DUMP
fileNumber = 1;
for (s = tapeBuffer; *s != '\0'; ++s) {
if (s[0] == '\n' && s[1] > ' ') {
++fileNumber;
}
}
#else
char *end;
int len = 0;
int n = 1;
/*
* Check each file on the dumptape, and pick the one that is the
* longest prefix of the file to be restored.
*/
if ((end = strchr(tapeBuffer, '\n')) == NULL) {
fatal("Bad tape label\n");
}
for (s = end; *s != '\0'; ++n) {
s = end + 1;
if ((end = strchr(s, '\n')) == NULL) {
break;
}
*end = '\0';
if (parseDumpInfo(s) == 0) {
fatal("format error in tapelabel, line %d\n", n);
}
assert(dumpDateEntry.file == n);
lastFile = n;
if (strlen(dumpDateEntry.dirname) >= len) {
if (strncmp(dumpDateEntry.dirname, progArgv[1],
strlen(dumpDateEntry.dirname)) == 0) {
len = strlen(dumpDateEntry.dirname);
fileNumber = n;
}
#if 0
else if (dumpDateEntry.dirname[0] == '/') {
if (strncmp(dumpDateEntry.dirname + 1, progArgv[1],
strlen(dumpDateEntry.dirname) - 1) == 0) {
len = strlen(dumpDateEntry.dirname) - 1;
fileNumber = n;
}
}
#endif
}
}
(void) fprintf(stderr, "Using file #%d\n", fileNumber);
if (fileNumber == 0) {
fatal("None of the files on this tape contain %s.\n", progArgv[1]);
}
#endif
return;
}
#ifdef PROG_RESTORE
static void
findLastFile()
{
char *s;
int n = 1;
char *end;
if ((end = strchr(tapeBuffer, '\n')) == NULL) {
fatal("Bad tape label\n");
}
for (s = end; *s != '\0'; ++n) {
s = end + 1;
if ((end = strchr(s, '\n')) == NULL) {
break;
}
*end = '\0';
lastFile = n;
}
}
#endif /* PROG_RESTORE */
static void
skipOverFiles()
{
int status;
int num;
#ifdef PROG_DUMP
Dev_TapeCommand args;
#endif
assert(fileNumber);
if (oldFormat) {
rewindTape();
num = fileNumber;
} else {
/*
* In the new format we don't rewind the tape. Instead we back
* up from where we currently are to the file we want.
*/
num = - (lastFile - fileNumber + 1) * 2;
}
status = skipFiles(num);
if (status != SUCCESS) {
fatal("Can't skip files, status = 0x%08x", status);
}
#ifdef PROG_DUMP
if (oldFormat) {
debugp((stderr, "Backing up over filemark\n"));
status = skipFiles(-1);
if (status != SUCCESS) {
fatal("Can't skip files, status = 0x%08x", status);
}
}
debugp((stderr, "rewriting file mark\n"));
args.command = IOC_TAPE_WEOF;
args.count = 1;
status = Fs_IOControl(archivefd, IOC_TAPE_COMMAND, sizeof(args),
(char *) &args, 0, (char *) 0);
#else
if (!oldFormat) {
/*
* Since we backed up we are on the BOT side of a file mark.
* Skip over it.
*/
debugp((stderr, "Skipping over filemark\n"));
status = skipFiles(1);
if (status != SUCCESS) {
fatal("Can't skip files, status = 0x%08x", status);
}
}
#endif
debugp((stderr, "successfully skipped %d files\n", fileNumber));
return;
}
/*ARGSUSED*/
static void
cleanup_sighup(sig)
int sig;
{
fatal("Received SIGHUP signal, terminating abnormally");
return;
}
/*ARGSUSED*/
static void
cleanup_sigint(sig)
int sig;
{
fatal("Received SIGINT signal, terminating abnormally");
return;
}
/*ARGSUSED*/
static void
cleanup_sigquit(sig)
int sig;
{
fatal("Received SIGQUIT signal, terminating abnormally");
return;
}
/*ARGSUSED*/
static void
cleanup_sigpipe(sig)
int sig;
{
#if 1
{
int w;
union wait ws;
debugp((stderr, "Received SIGPIPE signal, terminating abnormally\n"));
while ((w = wait(&ws)) > 0 && w != tarChild) {
continue;
}
debugp((stderr, "SIGPIPE: tar exited with code = 0x%x\n", ws.w_retcode));
if (ws.w_retcode) {
warning("tar exited with nozero status: %d", ws.w_retcode);
++fatalErrors;
}
(void) close(archivefd);
}
#endif
fatal("Received SIGPIPE signal, terminating abnormally");
return;
}
/*ARGSUSED*/
static void
cleanup_sigterm(sig)
int sig;
{
fatal("Received SIGTERM signal, terminating abnormally");
return;
}
/*
*----------------------------------------------------------------------
*
* sendMail --
*
* Results:
* None.
*
* Side effects:
* Sends mail to the person specified by the -m option.
*
*----------------------------------------------------------------------
*/
static void
sendMail(msg)
const char *msg;
{
int p[2];
int childPid;
int w;
FILE *fp;
if (mailWho == NULL) {
return;
}
pipe(p);
fprintf(stderr, "sending mail to `%s'\n", mailWho);
switch (childPid = fork()) {
case -1:
fatal("Fork failed");
case 0: /* child */
close(pipefd[1]);
dup2(pipefd[0], 0);
execlp("mail", "mail", mailWho, NULL);
fatal("Can't exec `mail'");
/* NOTREACHED */
default: /* parent */
close(pipefd[0]);
fp = fdopen(pipefd[1], "w");
fprintf(fp, "%s.\n\n", msg);
fprintf(fp, "Level %d dump of %s on %s",
dumpLevel, directoryToDump,
asctime(localtime(&startTime)));
(void) fclose(fp);
while ((w = wait(0)) > 0 && w != childPid)
;
fprintf(stderr, "done sending mail\n");
break;
}
return;
}
/*
*----------------------------------------------------------------------
*
* fatal --
*
* Print an error message and terminate the program.
*
* Results:
* None.
*
* Side effects:
* A formated error message is printed to stderr, and
* the program is terminated.
*
*----------------------------------------------------------------------
*/
#ifndef lint
static void
fatal(va_alist)
va_dcl
{
char *errmsg;
va_list args;
char mailBuf[0x1000];
char *fmt;
errmsg = strerror(errno);
va_start(args);
fmt = va_arg(args, char *);
(void) fprintf(stderr, "Dump: ");
(void) vfprintf(stderr, fmt, args);
(void) fprintf(stderr, ": %s\n", errmsg);
(void) fflush(stderr);
(void) vsprintf(mailBuf, fmt, args);
(void) strcat(mailBuf, ": ");
(void) strcat(mailBuf, errmsg);
sendMail(mailBuf);
va_end(args);
exit(1);
}
#else
/*VARARGS1*/
/*ARGSUSED*/
void
fatal(fmt)
char *fmt;
{
return;
}
#endif /* !lint */
/*
*----------------------------------------------------------------------
*
* warning --
*
* Print a warning message.
*
* Results:
* None.
*
* Side effects:
* A formated error message is printed to stderr.
*
*----------------------------------------------------------------------
*/
#ifndef lint
static void
warning(va_alist)
va_dcl
{
const char *errmsg;
va_list args;
char *fmt;
errmsg = strerror(errno);
va_start(args);
fmt = va_arg(args, char *);
(void) fprintf(stderr, "Dump: ");
(void) vfprintf(stderr, fmt, args);
(void) fprintf(stderr, ": %s\n", errmsg);
(void) fflush(stderr);
va_end(args);
return;
}
#else
/*VARARGS1*/
/*ARGSUSED*/
void
warning(fmt)
char *fmt;
{
return;
}
#endif /* !lint */
/*
*----------------------------------------------------------------------
*
* openLog --
*
* Open the dump log file and tee stderr so that all
* error messages are appended to the log.
*
* Results:
* None.
*
* Side effects:
* The log file is opened, and stderr is redirected to it.
*
*----------------------------------------------------------------------
*/
static void
openLog()
{
int pfd[2];
char buf[BUFSIZ];
int fd;
pipe(pfd);
switch (fork()) {
case -1:
fatal("fork failed");
/* NOTREACHED */
case 0: /* child */
close(pfd[1]);
if ((fd = open(LOGFILE, O_APPEND | O_WRONLY | O_CREAT, 0666)) < 0) {
warning("Can't open log file %s", LOGFILE);
return;
}
for (;;) {
int r;
switch (r = read(pfd[0], buf, sizeof(buf))) {
case -1:
fatal("Error reading from pipe");
/* NOTREACHED */
case 0:
close(fd);
exit(0);
default:
write(2, buf, r);
if (write(fd, buf, r) != r) {
warning("Error writing to %s", LOGFILE);
}
continue;
}
}
/* NOTREACHED */
default: /* parent */
close(pfd[0]);
dup2(pfd[1], 2); /* write all errors to log */
break;
}
return;
}
/*
*----------------------------------------------------------------------
*
* quote_string --
*
* Expand all control characters in a string to the equivalent
* backslashed excape sequence. For instance, a tab is converted
* to the two characters '\' and 't'.
*
* Results:
* None.
*
* Side effects:
* Modified the destination string.
*
*----------------------------------------------------------------------
*/
static void
quote_string(to, from)
char *to;
const char *from;
{
int c;
for (;;) {
switch (c = *from++) {
case '\0':
*to = '\0';
return;
case '\\':
*to++ = '\\';
*to++ = '\\';
break;
case '\n':
*to++ = '\\';
*to++ = 'n';
break;
case '\t':
*to++= '\\';
*to++= 't';
break;
case '\f':
*to++= '\\';
*to++= 'f';
break;
case '\b':
*to++= '\\';
*to++= 'b';
break;
default:
if (c < 0x20) {
*to++ = '\\';
*to++ = '0' + ((c >> 3) & 7);
*to++ = '0' + (c & 7);
break;
} else {
*to++ = c;
}
}
}
}
/*
*----------------------------------------------------------------------
*
* checkTape --
*
* Check the dumpdates file to make sure it is safe to initialize
* the tape. We do this by grepping through the last 100 lines
* of the dumpdates file to see if the tape has been used.
*
* Results:
* None.
*
* Side effects:
* Will exit with failure if it is not safe.
*
*----------------------------------------------------------------------
*/
static void
checkTape()
{
char buf[1000];
int status;
sprintf(buf,"tail -100 %s | grep \"^%3.3d\" > /dev/null", DUMPDATES,
tapeNumber);
status = system(buf);
if (status==0) {
fprintf(stderr,"dump: tape %d recently used; init failed\n",
tapeNumber);
} else if (status==256 || status==1) {
return;
} else {
fprintf(stderr,"dump: unable to do dumpdates safety check\n");
}
exit(123);
}
/*
*----------------------------------------------------------------------
*
* gotoEOD --
*
* Move tape to end-of-data.
*
* Results:
* None.
*
* Side effects:
* The tape is moved.
*
*----------------------------------------------------------------------
*/
static void
gotoEOD()
{
Dev_TapeCommand args;
ReturnStatus status;
int files;
debugp((stderr, "skipping to end of data\n"));
args.command = IOC_TAPE_SKIP_EOD;
args.count = 0;
status = Fs_IOControl(archivefd, IOC_TAPE_COMMAND, sizeof(args),
(char *) &args, 0, NULL);
if (status != SUCCESS) {
warning("Skip to end of data returned 0x%x", status);
/*
* If we are doing a restore then we want to do our best to
* find the end of usable data. Otherwise we return an error.
*/
fprintf(stderr, "Trying to find last file on tape\n");
files = 0;
rewindTape();
/*
* Skip over initial label.
*/
status = skipFiles(1);
if (status == SUCCESS) {
files++;
/*
* Now skip over pairs of dump files and labels.
*/
while(1) {
status = skipFiles(2);
if (status != SUCCESS) {
break;
}
files += 2;
}
}
fprintf(stderr, "Tape ends with file %d\n", files);
rewindTape();
status = skipFiles(files);
if (status != SUCCESS) {
fatal("Can't find end of tape");
}
}
}
/*
*----------------------------------------------------------------------
*
* skipFiles --
*
* Skip the given number of files.
*
* Results:
* Return status from the ioctl.
*
* Side effects:
* The tape is moved around.
*
*----------------------------------------------------------------------
*/
static ReturnStatus
skipFiles(num)
int num; /* Number of files to skip. */
{
ReturnStatus status;
Dev_TapeCommand args;
debugp((stderr, "skipping %d files\n", num));
args.command = IOC_TAPE_SKIP_FILES;
args.count = num;
status = Fs_IOControl(archivefd, IOC_TAPE_COMMAND, sizeof(args),
(char *) &args, 0, (char *) 0);
return status;
}